package edu.predatorwars.server.host;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.ws.rs.core.MediaType;

import org.quartz.DateBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import edu.predatorwars.server.engine.GameEngine;
import edu.predatorwars.shared.models.*;
import static org.quartz.DateBuilder.*;

public class GameHostImpl implements GameHostInterface, Runnable {

	/*
	 * Acts as central host for players
	 */

	private SchedulerFactory schedulerFactory;
	private ArrayList<Player> playerList;
	private static ArrayList<GameInstance> gameList;
	private Scheduler scheduler;
	private final int timeOutSeconds = 60;
	private final int retries = 5;

	final int numPlayers = 2;

	public GameHostImpl() {
		gameList = new ArrayList<GameInstance>();
		schedulerFactory = new StdSchedulerFactory();
		try {
			scheduler = schedulerFactory.getScheduler();
		} catch (SchedulerException e) {
			System.out.println("Could not get the scheduler.");
		}
		try {
			scheduler.start();
		} catch (SchedulerException e) {
			System.out.println("Could not start the scheduler.");
		}
	}

	public void run() {
		new GameHostImpl();
	}

	public Boolean createGame(Player joiningPlayer) {
		playerList = new ArrayList<Player>();
		playerList.add(joiningPlayer);
		startGame(new GameEngine(true, playerList));
		return true;
	}


	public synchronized Boolean joinGame(PlayerGamePayload payload) {
		Player joiningPlayer = payload.getPlayer();
		UUID gameId = payload.getGameId();
		Boolean joined = new Boolean(false);
		for (GameInstance game : gameList) {
			if (gameId.compareTo(game.getUUID()) == 0) {
				GameEngine engine = game.getGameEngine();
				joined = new Boolean(engine.addPlayer(joiningPlayer));
				if (engine.getMaxPlayers() == engine.getNumNotObservers()) {
					System.out.println("Starting game...");
					engine.doAllPlayersAdded();
				}
			}
		}	
		return joined;
	}

	// WEB SERVICE CLIENT CALLS
	public Boolean selectCharacter(SelectCharacterPayload payload) {
		Creature creature = payload.getCreature();
		Player player = payload.getPlayer();
		UUID gameId = payload.getGameId();
		for (GameInstance game : gameList) {
			if (gameId.compareTo(game.getUUID()) == 0) {
				game.getGameEngine().setCreature(player, creature.getCreatureType());
				game.getGameEngine().doAllCreaturesSelected();
			}
		}
		return true;
	}

	public Boolean quitGame(PlayerGamePayload payload) {
		Player quittingPlayer = payload.getPlayer();
		UUID gameId = payload.getGameId();
		for (GameInstance game : gameList) {
			if (game.getUUID().compareTo(gameId) == 0) {
				synchronized(game.getGameEngine()) { //restrict access to game engine
					game.getGameEngine().removePlayer(quittingPlayer);
				}
			}
		}
		return true;
	}

	private void startGame(GameEngine gameEngine) {
		GameInstance game = new GameInstance(this, UUID.randomUUID(),
				gameEngine);
		gameEngine.addGameListener(game); // The game is subscribed as a board
		// listener to the engine
		gameList.add(game); // Host now has the game in a list of games...right
		// now, no functionality to this
	}

	protected void latestUpdate(Message msg, List<Player> players) {
		System.out.println("Sending out message..." + msg.getType());
		PlayerConnectionInfo connection = null;
		String resourceUrl = "";
		// sends message payload to clients
		ClientConfig clientConfig = new DefaultClientConfig();
		clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
		Client client = Client.create(clientConfig);

		for (Player player : players) {
			connection = player.getCommInfo();
			resourceUrl = connection.getResourceUrl();
			WebResource message = client.resource(resourceUrl);
			ClientResponse response = message.accept("application/json").type(MediaType.APPLICATION_JSON).post(ClientResponse.class, msg);

		}

		if (msg instanceof BoardPayload) {
			System.out.println("Board payload is being sent out");
			//schedule the timeout
			UUID snapshot = ((BoardPayload) msg).getBoardSnapshot();
			UUID gameId = msg.getGameId();
			JobDetail job = newJob(TimeoutJob.class).withIdentity(snapshot.toString(), gameId.toString()).build();
			Trigger trigger = newTrigger().withIdentity(UUID.randomUUID().toString(), gameId.toString())
			.startAt(DateBuilder.futureDate(timeOutSeconds, IntervalUnit.SECOND)).build();

			try {
				scheduler.scheduleJob(job, trigger);
				System.out.println("Scheduled at: " + new Date());
			} catch (SchedulerException e) {
			}
		}
	}

	public Boolean move(MovementPayload movement) {
		Player player = movement.getPlayer();
		Move move = movement.getMove();
		UUID gameId = movement.getGameId();
		UUID boardSnapshot = movement.getSnapshotId();

		// player makes move, redirect to game engine
		GameInstance theGame = null;
		boolean result = false;
		for (GameInstance game : gameList) {
			if (gameId.compareTo(game.getUUID()) == 0) {
				theGame = game;
			}
		}
		
		synchronized(theGame.getGameEngine()) { //restrict access
			result = theGame.getGameEngine().doMove(player, move, boardSnapshot);
		}
		
		if (result) { //move was valid and board was up to date
			return true;
		} else { //move was not valid and board was not up to date, resend board
			theGame.boardChanged(theGame.getGameEngine(), theGame.getGameEngine().getBoard());
			return false;
		}
	}

	public GameListPayload refreshGameList() { //WEB SERVICE
		List<GameInfo> openGames = new ArrayList<GameInfo>();
		for (GameInstance game : gameList) {
			GameEngine engine = game.getGameEngine();
			if (engine.getGameState() == 0) { //0 means game is in setup stage
				GameInfo gameInfo = new GameInfo(game.getUUID(), engine.getMaxPlayers(), engine.getNumNotObservers(), engine.getBoardName());
				openGames.add(gameInfo);
			}
		}
		return new GameListPayload(openGames);

	}

	/**
	 * Called by scheduled TimeoutJob
	 * @param gameId
	 * @param snapshotId
	 */
	public synchronized static void timeoutPlayer(UUID snapshotId, UUID gameId) {
		for (GameInstance game : gameList) {
			if (game.getUUID().equals(gameId) 
					&& game.getGameEngine().getBoardSnapshot().equals(snapshotId)) {
				GameEngine engine = game.getGameEngine();
				Player deliquentPlayer = engine.getCurrentPlayer();
				synchronized (engine) {
					System.out.println("Removing player: " + deliquentPlayer.getCommInfo().getName());
					engine.removePlayer(deliquentPlayer);
					System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + engine.getBoardSnapshot());
				}
				return;
			}
		}
	}
	
	public void gameEnding(UUID gameId, Message msg, List<Player> players) {
		System.out.println("ending game..");
		latestUpdate(msg, players);
		for (GameInstance game : gameList) {
			if (gameId.equals(game.getUUID())) { //game is over, remove from list
				gameList.remove(game);
				break;
			}
		}
	}


	/**
	 * For testing
	 * @param args
	 */
	public static void main(String args[]) {
		GameHostImpl host = new GameHostImpl();
		Player player1 = new Player(new PlayerConnectionInfo("Russell0", "1234"),new float[] {0.5f,0.0f,0.2f});
		host.createGame(player1);
		host.createGame(player1);
		host.createGame(player1);
		GameListPayload payload = host.refreshGameList();
		List<GameInfo> openGames = payload.getGameList();

		System.out.println("Checking games: ");
		for (GameInfo i : openGames) {
			System.out.println("Max players: " +i.getMaxPlayers());
			System.out.println("Game id: " + i.getGameId());
			System.out.println("Current players: " + i.getCurrentPlayers());
		}
		GameInstance instance = gameList.get(0);
		UUID gameId = instance.getUUID();

		Player player2 = new Player(new PlayerConnectionInfo("Russell", "12345"),new float[] {0.5f,0.0f,0.2f});
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());
		Player player3 = new Player(new PlayerConnectionInfo("Russell2", "12356"),new float[] {0.5f,0.0f,0.2f});
		Player player4 = new Player(new PlayerConnectionInfo("Russell3", "1237"),new float[] {0.5f,0.0f,0.2f});
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());

		Creature[] creatures = instance.getGameEngine().getAvailableCreatures();
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());

		host.joinGame(new PlayerGamePayload(player2, gameId));
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());
		host.joinGame(new PlayerGamePayload(player3, gameId));
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());
		host.joinGame(new PlayerGamePayload(player4, gameId));
		System.out.println("Game size: " + gameList.get(0).getGameEngine().getCurrentPlayers().size());

		host.selectCharacter(new SelectCharacterPayload(player1, creatures[0], gameId));
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());
		host.selectCharacter(new SelectCharacterPayload(player2, creatures[0], gameId));
		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());
		host.selectCharacter(new SelectCharacterPayload(player3, creatures[0], gameId));
		host.selectCharacter(new SelectCharacterPayload(player4, creatures[0], gameId));

		System.out.println("The UUIDs, game: " + gameId + " and the snapshot + " + instance.getGameEngine().getBoardSnapshot());

		payload = host.refreshGameList();
		openGames = payload.getGameList();

		System.out.println("Checking games: ");
		for (GameInfo i : openGames) {
			System.out.println("Max players: " +i.getMaxPlayers());
			System.out.println("Game id: " + i.getGameId());
			System.out.println("Current players: " + i.getCurrentPlayers());
		}
	}
}

